package com.gmrz.uaf.servlet.v1.handler;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.service.challengestore.ChallengeStoreService;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.common.UAFException;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFAuthFinishProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.AdditionUviException;
import com.gmrz.uaf.protocol.v1.processor.exception.ExactMatchNotFoundException;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPAuthFinishProcessor;
import com.gmrz.uaf.remote.protocol.v1.processor.UAPCertAuthFinishProcessor;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.AuthSendContext;
import com.gmrz.uaf.servlet.v1.request.ReceiveReqPayload;
import com.gmrz.uaf.servlet.v1.response.FidoRespPayload;
import com.gmrz.uaf.servlet.v1.validation.PayloadValidationException;
import com.gmrz.uaf.servlet.v1.validation.RestValidator;
import com.gmrz.uas.plugin.exception.AuthNoMatchPluginBusinessException;
import com.gmrz.util.Convert;
import com.google.common.base.Strings;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.lang.reflect.Type;
import java.util.*;

public class UAFAuthResponseHandler extends UAFBaseHandler {
    private static final Logger LOG = LogManager.getLogger(UAFAuthResponseHandler.class);

    /**
     * {
     * "uafResponse":"[{...}]",
     * "context":{
     * "transNo":"32characters",
     * "appID","001",
     * }
     * }
     *
     * @param request
     * @return {
     * "statusCode":1200,
     * "description":"OK"
     * }
     */
    public String authFinish(String request) {
        LOG.info("[/auth/send][begin]");
        ServerContext.getServerContext().getFidoLog().setRequest(request);
        String response = null;
        Gson gson = UAFSchemaBuilder.getGson();
        FidoRespPayload<String> respPayload = new FidoRespPayload<String>();

        Type reqType = new TypeToken<ReceiveReqPayload<AuthSendContext>>() {
        }.getType();
        String authErrorCountKey = null;
        try {
            ReceiveReqPayload<AuthSendContext> reqPayload = gson.fromJson(request, reqType);
            ServerContext.getServerContext().setPerform(reqPayload.getContext().getAppID());
            ServerContext.getServerContext().setBase64(reqPayload.getContext().isBase64());

            String uafMessage = null;
            if(ServerContext.getServerContext().isBase64()) {
                uafMessage = new String(Convert.fromBase64(reqPayload.getUafResponse()));
            }else {
                uafMessage = reqPayload.getUafResponse();
            }

            Type type = new TypeToken<List<AuthenticationResponse>>() {
            }.getType();
            List<String> authTypes = reqPayload.getContext().getAuthType();
            if(null != authTypes) {
                Iterator<String> it = authTypes.iterator();
                while (it.hasNext()){
                    String auth = it.next();
                    // 判断认证方式中是否存在 二维码 方式
                    if(auth.equals(Constants.AuthType.REMOTE_OOBAUTH.getAuthType())){
                        it.remove();
                    }
                }
            }
            if(null != reqPayload.getContext().getAuthType() && !reqPayload.getContext().getAuthType().isEmpty() && reqPayload.getContext().getAuthType().get(0).compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0){
                gson = UAFSchemaBuilder.getGsonProcessCert();
            }
            List<AuthenticationResponse> authResponses = gson.fromJson(uafMessage, type);
            //获取当前版本的response参数
            AuthenticationResponse authResponse = null;
            if ((authResponses != null) && (!authResponses.isEmpty())) {
                if (authResponses.size() > 1) {
                    authResponse = RestValidator.compareAuthRespVersions(authResponses);
                } else {
                    authResponse = authResponses.get(0);
                }
            }


            if (null == authResponse) {
                LOG.error("uafResponse is Null");
                throw new PayloadValidationException("uafResponse", RestValidator.VALIDATION_ERROR.EMPTY);
            }
            AuthSendContext context = reqPayload.getContext();
            // hash 用户名
            String hashedUserName = generateHashedUserName(context.getUserName(), context.getRf1(), context.getRf2());
            if(this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !ServerContext.getServerContext().isPerform()) {
                // 验证发起接口参数和完成接口参数是否一致
                validate(reqPayload, authResponse, hashedUserName);
            }
            //记录日志
            logRequest(request, reqPayload.getContext().getTransNo(), reqPayload.getContext().getUserName(),
                    reqPayload.getContext().getAppID(), reqPayload.getContext().getAuthType().get(0),reqPayload.getContext().getTransType(), Constants.Method.AUTH_FINISH);
            if(this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_1 && !ServerContext.getServerContext().isPerform()) {
                //验证context参数
                validate(reqPayload);
            }
            UAFAuthFinishProcessor processor = null;
            if (reqPayload.getContext().getAuthType() != null &&
                    reqPayload.getContext().getAuthType().get(0).compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {

                processor = GuiceUtil.getProcessorInjector().getInstance(UAPCertAuthFinishProcessor.class);
                AuthenticationAssertion assertion = authResponse.getAuthenticatorAssertions().get(0);
                String assertionAAID = assertion.getSignResponse().getSignData().getAAID();
                // 根据 assertion 中的 AAID 获取身份认证规范（身份认证规范对应的数据就是元数据中的数据）
                AuthenticatorSpec spec = assertionAAID == null ? null : processor.getAuthenticatorSpec(assertionAAID);
                if(null != spec){
                    // 将获取到的身份认证规范中的算法 赋值给 assertion中的 代签名的原数据
                    assertion.getSignResponse().getSignData().withSignAlgorithm(spec.getAlgorithm());
                }
            }else if (reqPayload.getContext().getAuthType() != null &&
                    reqPayload.getContext().getAuthType().get(0).compareTo(Constants.AuthType.REMOTE_FACEPRINT.getAuthType()) >= 0) {
                processor = GuiceUtil.getProcessorInjector().getInstance(UAPAuthFinishProcessor.class);
            } else {
                processor = GuiceUtil.getProcessorInjector().getInstance(UAFAuthFinishProcessor.class);
            }
            String keyId = "";
            if(null != authResponse.getAuthenticatorAssertions() && authResponse.getAuthenticatorAssertions().size() > 0){
                // 使用base64将字节数组安全的转为字符串
                keyId = Convert.toBase64(authResponse.getAuthenticatorAssertions().get(0).getSignResponse().getSignData().getKeyID());
            }
            authErrorCountKey = Constants.AUTH_ERROR_COUNT+keyId+"."+context.getAuthType().get(0)+"."+context.getTransType()+"."+hashedUserName;

            //　进行业务逻辑处理
            processor.process(authResponse, context.getAppID(), hashedUserName, reqPayload.getContext().getAuthType().get(0), reqPayload.getContext().getTransType(),reqPayload.getContext().getExt(),context.getExtra());
            respPayload.setStatusCode(Constants.StatusCode.OK.getCode());
            respPayload.setDescription("OK");
            if (reqPayload.getContext().getAuthType() != null &&
                    reqPayload.getContext().getAuthType().get(0).compareTo(Constants.AuthType.CERT_FINGERPRINT.getAuthType()) >= 0) {
                Map<String,String> exts = new HashMap<String, String>();
                // 将 p7 交易内容 数据放到 响应中的扩展中
                exts.put("p7data",Convert.toBase64(authResponse.getAuthenticatorAssertions().get(0).getSignResponse().getP7Bytes()));
                respPayload.setExts(exts);
            }

            response = gson.toJson(respPayload);

            ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
            challengeStoreService.deleteRedisValue(authErrorCountKey);
        }catch (AdditionUviException ex) {
            response = buildFidoRespFromException(respPayload, ex);
        }catch (AuthNoMatchPluginBusinessException ex) {
            setAuthErrorCount(authErrorCountKey,respPayload,gson);
            response = buildFidoRespHasUafRequestFromException(respPayload, new ExactMatchNotFoundException(
                    UAFErrorCode.PROTOCOL_EXACT_MATCH_NOT_FOUND));
        }catch (UAFException ex){
            if(null != ex.getErrorCode() && ex.getErrorCode().getStatusCode() == 3028){
                setAuthErrorCount(authErrorCountKey,respPayload,gson);
            }
            response = buildFidoRespHasUafRequestFromException(respPayload, ex);
        }catch (Exception ex) {
            LOG.error("authFinish error",ex);
            response = buildFidoRespFromException(respPayload, ex);
        }
        LOG.info("[/auth/send][end]");
        return response;
    }

    /**
     * 验证context参数
     * @param payload
     * @throws PayloadValidationException
     */
    void validate(ReceiveReqPayload<AuthSendContext> payload) throws PayloadValidationException {
        if ((null == payload) || (Strings.isNullOrEmpty(payload.getUafResponse()))) {
            LOG.error("Payload does not contains uafResponse");
            throw new PayloadValidationException("uafResponse", RestValidator.VALIDATION_ERROR.EMPTY);
        }
        RestValidator.validate("uafResponse", payload.getUafResponse());
        RestValidator.validate("appID", payload.getContext().getAppID());
        RestValidator.validate("userName", payload.getContext().getUserName());
        RestValidator.validate("transNo", payload.getContext().getTransNo());

        RestValidator.validate("rf1", payload.getContext().getRf1());
        RestValidator.validate("rf2", payload.getContext().getRf2());

        RestValidator.validate("authType", payload.getContext().getAuthType().get(0));
        RestValidator.validate("transType", payload.getContext().getTransType());
        RestValidator.validate("ext", payload.getContext().getExt());
        RestValidator.validate("extra", payload.getContext().getExtra());

    }

    /**
     * 验证发起接口参数和完成接口参数是否一致
     * @param payload
     * @param regResponse
     * @param hashedUserName
     * @throws ChallengeStoreException
     * @throws PayloadValidationException
     */
    void validate(ReceiveReqPayload<AuthSendContext> payload, AuthenticationResponse regResponse, String hashedUserName) throws ChallengeStoreException, PayloadValidationException {
        AuthSendContext context = payload.getContext();
        String challenge = Convert.toBase64(regResponse.getFinalChallengeParams().getServerChallenge().getChallenge());
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        String paramCache = challengeStoreService.getRedisValue(Constants.UAP_AUTH_CONTEXT_CACHE_KEY + challenge + "-" + hashedUserName);
        if (null == paramCache || "".equals(paramCache.trim())) {
            throw new PayloadValidationException("send context and receive context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
        } else {
            if(null == payload.getContext().getAuthType()){
                String[] strs = paramCache.split(",");
                for(String str : strs){
                    if(str.startsWith("authType")){
                        String[] ats = str.split(":");
                        List<String> list = new ArrayList<String>();
                        if(2 == ats[1].length()) {
                            list.add(ats[1]);
                        }else{
                            list.add(ats[1].substring(0,2));
                        }
                        payload.getContext().setAuthType(list);
                    }
                    if(str.startsWith("transType")){
                        String[] ats = str.split(":");
                        payload.getContext().setTransType(ats[1]);
                    }
                }
            }else {
                String param = getValidateData(context.getUserName(), context.getRf1(), context.getRf2(),
                        context.getAppID(), null, context.getTransType(), context.getAuthType(),context.getChallenge());
                LOG.debug("auth validate context param:" + param + "---paramcache" + paramCache);
                if (!param.equals(paramCache)) {
                    throw new PayloadValidationException("send context and receive context", RestValidator.VALIDATION_ERROR.DIFFERENCE);
                }
            }
        }
        Iterator<String> it = context.getAuthType().iterator();
        while (it.hasNext()){
            if(Constants.AuthType.REMOTE_OOBAUTH.getAuthType().equals(it.next())){
                it.remove();
            }
        }
    }

    private void setAuthErrorCount(String authErrorCountKey,FidoRespPayload<String> respPayload,Gson gson){
        ChallengeStoreService challengeStoreService = GuiceUtil.getProcessorInjector().getInstance(ChallengeStoreService.class);
        Map<String,String> exts = null;
        try {
            long count = challengeStoreService.incr(authErrorCountKey);
            //challengeStoreService.expire(authErrorCountKey,2592000);
            exts = new HashMap<String, String>();
            exts.put("authErrorCount",String.valueOf(count));

        } catch (Exception e) {
            LOG.error("authFinish ChallengeStoreService error",e);
        }
        if(null != exts) {
            respPayload.setExts(exts);
        }
    }
}
